{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "53c04c31",
   "metadata": {},
   "source": [
    "# 原则三：依赖倒置原则（Dependency Inversion Principle）\n",
    "\n",
    "## 定义\n",
    "- Depend upon Abstractions. Do not depend upon concretions.\n",
    "- Abstractions should not depend upon details. Details should depend upon abstractions\n",
    "- High-level modules should not depend on low-level modules. Both should depend on abstractions.\n",
    "\n",
    "即：\n",
    "\n",
    "- 依赖抽象，而不是依赖实现。\n",
    "- 抽象不应该依赖细节；细节应该依赖抽象。\n",
    "- 高层模块不能依赖低层模块，二者都应该依赖抽象。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "379d73be",
   "metadata": {},
   "source": [
    "## 优点\n",
    "通过抽象来搭建框架，建立类和类的关联，以减少类间的耦合性。而且以抽象搭建的系统要比以具体实现搭建的系统更加稳定，扩展性更高，同时也便于维护。\n",
    "\n",
    "## 代码讲解\n",
    "下面通过一个模拟项目开发的例子来讲解依赖倒置原则。\n",
    "\n",
    "### 需求点\n",
    "实现下面这样的需求：\n",
    "\n",
    "用代码模拟一个实际项目开发的场景：前端和后端开发人员开发同一个项目。\n",
    "\n",
    "### 不好的设计\n",
    "\n",
    "首先生成两个类，分别对应前端和后端开发者：\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "a2786e7f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 前端开发者：\n",
    "\n",
    "class FrondEndDeveloper(object):\n",
    "    def writeJavaScriptCode(self):\n",
    "        print(\"Write JavaScript code\")\n",
    "        \n",
    "# 后端开发者：\n",
    "class BackEndDeveloper(object):\n",
    "    def writeJavaCode(self):\n",
    "        print(\"Write Java code\")\n",
    "        \n",
    "        \n",
    "# 这两个开发者分别对外提供了自己开发的方法：writeJavaScriptCode和writeJavaCode。\n",
    "\n",
    "# 接着创建一个Project类：\n",
    "class Project(object):\n",
    "    def __init__(self, developers):\n",
    "        self.developers = developers\n",
    "        \n",
    "    def startDeveloping(self):\n",
    "        for developer in self.developers:\n",
    "            if isinstance(developer, FrondEndDeveloper):\n",
    "                developer.writeJavaScriptCode()\n",
    "            elif isinstance(developer, BackEndDeveloper):\n",
    "                developer.writeJavaCode()\n",
    "            else:\n",
    "                # no such developer\n",
    "                pass"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1da95116",
   "metadata": {},
   "source": [
    "在Project类中，我们首先通过一个构造器方法，将开发者的数组传入project的实例对象。然后在开始开发的方法startDeveloping里面，遍历数组并判断元素类型的方式让不同类型的开发者调用和自己对应的函数。\n",
    "\n",
    "思考一下，这样的设计有什么问题？\n",
    "\n",
    "问题一：\n",
    "\n",
    "假如后台的开发语言改成了GO语言，那么上述代码需要改动两个地方：\n",
    "\n",
    "BackEndDeveloper:需要向外提供一个writeGolangCode方法。\n",
    "Project类的startDeveloping方法里面需要将BackEndDeveloper类的writeJavaCode改成writeGolangCode。\n",
    "\n",
    "\n",
    "问题二：\n",
    "\n",
    "假如后期老板要求做移动端的APP（需要iOS和安卓的开发者），那么上述代码仍然需要改动两个地方：\n",
    "\n",
    "还需要给Project类的构造器方法里面传入IOSDeveloper和AndroidDeveloper两个类。而且按照现有的设计，还要分别向外部提供writeSwiftCode和writeKotlinCode。\n",
    "Project类的startDeveloping方法里面需要再多两个elseif判断，专门判断IOSDeveloper和AndroidDeveloper这两个类。\n",
    "\n",
    "    开发安卓的代码也可以用Java，但是为了和后台的开发代码区分一下，这里用了同样可以开发安卓的Kotlin语言。\n",
    "    \n",
    "很显然，在这两种假设的场景下，高层模块（Project）都依赖了低层模块（BackEndDeveloper）的改动，因此上述设计不符合依赖倒置原则。\n",
    "\n",
    "那么该如何设计才可以符合依赖倒置原则呢？\n",
    "\n",
    "答案是将开发者写代码的方法抽象出来，让Project类不再依赖所有低层的开发者类的具体实现，而是依赖抽象。而且从下至上，所有底层的开发者类也都依赖这个抽象，通过实现这个抽象来做自己的任务。\n",
    "\n",
    "这个抽象可以用接口，也可以用抽象类的方式来做，在这里笔者用使用接口的方式进行讲解：\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28899ecf",
   "metadata": {},
   "source": [
    "## 较好的设计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "1d890b3e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 首先，创建一个接口，接口里面有一个写代码的方法writeCode：（Python 就采用继承了）\n",
    "\n",
    "class Developer(object):\n",
    "    def writeCode(self):\n",
    "        print(f\"子类必须实现 writeCode 方法\")\n",
    "        raise NotImplementedError\n",
    "        \n",
    "class FrondEndDeveloper(Developer):\n",
    "    def writeCode(self):\n",
    "        print(\"Write JavaScript code\")\n",
    "        \n",
    "class BackEndDeveloper(Developer):\n",
    "    def writeCode(self):\n",
    "        print(\"Write Java code\")\n",
    "        \n",
    "# 接着创建一个Project类：\n",
    "class Project(object):\n",
    "    def __init__(self, developers):\n",
    "        self.developers = developers\n",
    "        \n",
    "    def startDeveloping(self):\n",
    "        for developer in self.developers:\n",
    "            developer.writeCode()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "126eb17c",
   "metadata": {},
   "source": [
    "新的Project的构造方法只需传入遵循DeveloperProtocol协议的对象构成的数组即可。这样也比较符合现实中的需求：只需要会写代码就可以加入到项目中。\n",
    "\n",
    "而新的startDeveloping方法里：每次循环，直接向当前对象发送writeCode方法即可，不需要对程序员的类型做判断。因为这个对象一定是遵循DeveloperProtocol接口的，而遵循该接口的对象一定会实现writeCode方法（就算不实现也不会引起重大错误）。\n",
    "\n",
    "现在新的设计接受完了，我们通过上面假设的两个情况来和之前的设计做个对比：\n",
    "\n",
    "**假设1：后台的开发语言改成了GO语言**\n",
    "\n",
    "在这种情况下，只需更改BackEndDeveloper类里面对于DeveloperProtocol接口的writeCode方法的实现即可："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "198e232c",
   "metadata": {},
   "outputs": [],
   "source": [
    "class BackEndDeveloper(Developer):\n",
    "    def writeCode(self):\n",
    "        # Old\n",
    "        # print(\"Write Java code\")\n",
    "        # New\n",
    "        print(\"Write golang code\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5760ed5",
   "metadata": {},
   "source": [
    "假设2：后期老板要求做移动端的APP（需要iOS和安卓的开发者）\n",
    "\n",
    "在这个新场景下，我们只需要将新创建的两个开发者类：IOSDeveloper和AndroidDeveloper分别实现DeveloperProtocol接口的writeCode方法即可。\n",
    "\n",
    "同样，Project的接口和实现代码都不用修改：客户端只需要在Project的构建方法的数组参数里面添加这两个新类的实例即可，不需要在startDeveloping方法里面添加类型判断，原因同上。\n",
    "\n",
    "我们可以看到，新设计很好地在高层类（Project）与低层类（各种developer类）中间加了一层抽象，解除了二者在旧设计中的耦合，使得在低层类中的改动没有影响到高层类。\n",
    "\n",
    "同样是抽象，新设计同样也可以用抽象类的方式：创建一个Developer的抽象类并提供一个writeCode方法，让不同的开发者类继承与它并按照自己的方式实现writeCode方法。这样一来，在Project类的构造方法就是传入已Developer类型为元素的数组了。有兴趣的小伙伴可以自己实现一下~\n",
    "\n",
    "下面来看一下这两个设计的UML 类图，可以更形象地看出两种设计上的区别：\n",
    "\n",
    "UML 类图对比\n",
    "\n",
    "未实践依赖倒置原则：\n",
    "![101](pic/103-101.png)\n",
    "\n",
    "实践了依赖倒置原则：\n",
    "![101](pic/103-102.png)\n",
    "\n",
    "在实践了依赖倒置原则的 UML 类图中，我们可以看到Project仅仅依赖于新的接口；而且低层的FrondEndDevelope和BackEndDevelope类按照自己的方式实现了这个接口：通过接口解除了原有的依赖。（在 UML 类图中，虚线三角箭头表示接口实线，由实现方指向接口）\n",
    "如何实践\n",
    "\n",
    "今后在处理高低层模块（类）交互的情景时，尽量将二者的依赖通过抽象的方式解除掉，实现方式可以是通过接口也可以是抽象类的方式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53a5d1cd",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d1da3ea",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ec9b742",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a835e60",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eda048a1",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50330504",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
